use crate::bson::{Binary, Document};
use mongocrypt::ctx::{Algorithm, CtxBuilder};

use crate::{
    action::{
        action_impl,
        csfle::encrypt::{Encrypt, EncryptKey, EncryptOptions, Expression, Value},
    },
    error::{Error, Result},
};

use super::ClientEncryption;

#[action_impl]
impl<'a> Action for Encrypt<'a, Value> {
    type Future = EncryptFuture;

    async fn execute(self) -> Result<Binary> {
        let ctx = self
            .client_enc
            .get_ctx_builder(self.key, self.algorithm, self.options.unwrap_or_default())?
            .build_explicit_encrypt(self.mode.value)?;
        let result = self.client_enc.exec.run_ctx(ctx, None).await?;
        let bin_ref = result
            .get_binary("v")
            .map_err(|e| Error::internal(format!("invalid encryption result: {e}")))?;
        Ok(bin_ref.to_binary())
    }
}

#[action_impl]
impl<'a> Action for Encrypt<'a, Expression> {
    type Future = EncryptExpressionFuture;

    async fn execute(mut self) -> Result<Document> {
        let options = self.options.get_or_insert_with(Default::default);
        match options.query_type {
            Some(ref query_type) => {
                if query_type != "range" {
                    return Err(Error::invalid_argument(format!(
                        "query_type cannot be set for encrypt_expression, got {query_type}"
                    )));
                }
            }
            None => options.query_type = Some("range".to_string()),
        }

        let ctx = self
            .client_enc
            .get_ctx_builder(self.key, self.algorithm, self.options.unwrap_or_default())?
            .build_explicit_encrypt_expression(self.mode.value)?;
        let result = self.client_enc.exec.run_ctx(ctx, None).await?;
        let doc_ref = result
            .get_document("v")
            .map_err(|e| Error::internal(format!("invalid encryption result: {e}")))?;
        let doc = Document::try_from(doc_ref.to_owned())
            .map_err(|e| Error::internal(format!("invalid encryption result: {e}")))?;
        Ok(doc)
    }
}

impl ClientEncryption {
    fn get_ctx_builder(
        &self,
        key: EncryptKey,
        algorithm: Algorithm,
        opts: EncryptOptions,
    ) -> Result<CtxBuilder> {
        let mut builder = self.crypt.ctx_builder();
        match key {
            EncryptKey::Id(id) => {
                builder = builder.key_id(&id.bytes)?;
            }
            EncryptKey::AltName(name) => {
                builder = builder.key_alt_name(&name)?;
            }
        }
        builder = builder.algorithm(algorithm)?;
        if let Some(factor) = opts.contention_factor {
            builder = builder.contention_factor(factor)?;
        }
        if let Some(qtype) = &opts.query_type {
            builder = builder.query_type(qtype)?;
        }
        if let Some(range_options) = &opts.range_options {
            let options_doc = crate::bson_compat::serialize_to_document(range_options)?;
            builder = builder.algorithm_range(options_doc)?;
        }
        #[cfg(feature = "text-indexes-unstable")]
        if let Some(text_options) = &opts.text_options {
            let options_doc = crate::bson_compat::serialize_to_document(text_options)?;
            builder = builder.algorithm_text(options_doc)?;
        }
        Ok(builder)
    }
}
